import type { TemporalSupportedObject } from '../base-ui-copy/types';
import { RecurringEventRecurrenceRule } from './recurringEvent';
import type { SchedulerOccurrencePlaceholderExternalDragData } from './dragAndDrop';
import type { SchedulerResourceId } from './resource';

export interface SchedulerProcessedEvent {
  /**
   * The unique identifier of the event.
   */
  id: SchedulerEventId;
  /**
   * The title of the event.
   */
  title: string;
  /**
   * The description of the event.
   */
  description?: string;
  /**
   * The start date and time of the event.
   */
  start: SchedulerProcessedDate;
  /**
   * The end date and time of the event.
   */
  end: SchedulerProcessedDate;
  /**
   * The id of the resource this event is associated with.
   */
  resource?: SchedulerResourceId | null;
  /**
   * The recurrence rule for the event.
   * If not defined, the event will have only one occurrence.
   */
  rrule?: RecurringEventRecurrenceRule;
  /**
   * Exception dates for the event.
   * These dates will be excluded from the recurrence.
   */
  exDates?: TemporalSupportedObject[];
  /**
   * Whether the event is an all-day event.
   * @default false
   */
  allDay?: boolean;
  /**
   * Whether the event is read-only.
   * Readonly events cannot be modified using UI features such as popover editing or drag and drop.
   * @default false
   */
  readOnly?: boolean;
  /**
   * The id of the original event from which this event was split.
   * If provided, it must reference an existing event in the calendar.
   * If it does not match any existing event, the value will be ignored
   * and no link to an original event will be created.
   */
  extractedFromId?: SchedulerEventId;
  /**
   * The event model in the `SchedulerEvent` format.
   */
  modelInBuiltInFormat: SchedulerEvent | null;
  /**
   * The color of the event.
   * Takes precedence over resource color if both are defined.
   */
  color?: SchedulerEventColor;
  /**
   * Whether the event is draggable.
   * If not defined, the event is draggable if the `areEventsDraggable` property is enabled.
   */
  draggable?: boolean;
  /**
   * Whether the event is resizable.
   * If `true`, both start and end can be resized.
   * If `false`, the event is not resizable.
   * If `"start"`, only the start can be resized.
   * If `"end"`, only the end can be resized.
   * If not defined, the event is resizable if the `areEventsResizable` property is enabled.
   */
  resizable?: boolean | SchedulerEventSide;
  /**
   * A custom class name to apply to the event element.
   */
  className?: string;
}

export interface SchedulerEvent {
  /**
   * The unique identifier of the event.
   */
  id: SchedulerEventId;
  /**
   * The title of the event.
   */
  title: string;
  /**
   * The description of the event.
   */
  description?: string;
  /**
   * The start date and time of the event.
   */
  start: TemporalSupportedObject;
  /**
   * The end date and time of the event.
   */
  end: TemporalSupportedObject;
  /**
   * The id of the resource this event is associated with.
   * @default null
   */
  resource?: SchedulerResourceId | null;
  /**
   * The recurrence rule for the event.
   * It can be provided either as a string (RFC5545 RRULE format)
   * or as a RecurringEventRecurrenceRule object.
   * If not defined, the event will have only one occurrence.
   */
  rrule?: RecurringEventRecurrenceRule | string;
  /**
   * Exception dates for the event.
   * These dates will be excluded from the recurrence.
   */
  exDates?: TemporalSupportedObject[];
  /**
   * Whether the event is an all-day event.
   * @default false
   */
  allDay?: boolean;
  /**
   * Whether the event is read-only.
   * Readonly events cannot be modified using UI features such as popover editing or drag and drop.
   * @default false
   */
  readOnly?: boolean;
  /**
   * The id of the original event from which this event was split.
   * If provided, it must reference an existing event in the calendar.
   * If it does not match any existing event, the value will be ignored
   * and no link to an original event will be created.
   */
  extractedFromId?: SchedulerEventId;
  /**
   * The color of the event.
   * Takes precedence over resource color if both are defined.
   */
  color?: SchedulerEventColor;
  /**
   * Whether the event is draggable.
   * If not defined, the event is draggable if the `areEventsDraggable` property is true.
   */
  draggable?: boolean;
  /**
   * Whether the event is resizable.
   * If not defined, the event is resizable if the `areEventsResizable` property is true.
   */
  resizable?: boolean | SchedulerEventSide;
  /**
   * A custom class name to apply to the event element.
   */
  className?: string;
}

/**
 *  A concrete occurrence derived from a `SchedulerEvent` (recurring or single).
 */
export interface SchedulerEventOccurrence extends SchedulerProcessedEvent {
  /**
   * Unique key that can be passed to the React `key` property when looping through events.
   */
  key: string;
}

export type SchedulerEventId = string | number;

export type SchedulerEventColor =
  | 'primary'
  | 'mauve'
  | 'violet'
  | 'cyan'
  | 'jade'
  | 'red'
  | 'lime'
  | 'orange'
  | 'yellow'
  | 'pink'
  | 'indigo'
  | 'blue';

export type SchedulerEventSide = 'start' | 'end';

interface SchedulerOccurrencePlaceholderBase {
  /**
   * The type of surface the draft should be rendered on.
   * This is useful to make sure the placeholder is only rendered in the correct grid.
   */
  surfaceType: EventSurfaceType;
  /**
   * The new start date and time of the event occurrence.
   */
  start: TemporalSupportedObject;
  /**
   * The new end date and time of the event occurrence.
   */
  end: TemporalSupportedObject;
  /**
   * The id of the resource onto which to drop the event.
   * If null, the event will be dropped outside of any resource.
   */
  resourceId: SchedulerResourceId | null;
  /**
   * Whether the occurrence placeholder should be hidden.
   * This is used when dragging an event outside of the calendar to avoid showing both the placeholder and the drag preview.
   */
  isHidden?: boolean;
}

export interface SchedulerOccurrencePlaceholderCreation extends SchedulerOccurrencePlaceholderBase {
  /**
   * The type of placeholder.
   */
  type: 'creation';
  /**
   * Whether to lock the surface type of the placeholder.
   * When true, the surfaceType will not be updated when editing the placeholder.
   */
  lockSurfaceType?: boolean;
}

export interface SchedulerOccurrencePlaceholderInternalDragOrResize
  extends SchedulerOccurrencePlaceholderBase {
  /**
   * The type of placeholder.
   */
  type: 'internal-drag' | 'internal-resize';
  /**
   * The id of the event being changed.
   */
  eventId: SchedulerEventId;
  /**
   * The key of the event occurrence being changed.
   */
  occurrenceKey: string;
  /**
   * The data of the event to use when dropping the event outside of the Event Calendar or the Timeline.
   */
  originalOccurrence: SchedulerEventOccurrence;
}

export interface SchedulerOccurrencePlaceholderExternalDrag
  extends SchedulerOccurrencePlaceholderBase {
  /**
   * The type of placeholder.
   */
  type: 'external-drag';
  /**
   * The data of the event to insert in the Event Calendar or the Timeline when dropped.
   */
  eventData: SchedulerOccurrencePlaceholderExternalDragData;
  /**
   * Callback fired when the event is dropped into the Event Calendar or the Timeline.
   */
  onEventDrop?: () => void;
}

/**
 * Object representing the placeholder of an event occurrence.
 * It is used when creating a new event or when dragging an event occurrence.
 */
export type SchedulerOccurrencePlaceholder =
  | SchedulerOccurrencePlaceholderCreation
  | SchedulerOccurrencePlaceholderInternalDragOrResize
  | SchedulerOccurrencePlaceholderExternalDrag;

export interface SchedulerProcessedDate {
  /**
   * The date object.
   */
  value: TemporalSupportedObject;
  /**
   * String representation of the date.
   * It can be used as key in Maps or passed to the React `key` property when looping through days.
   * It only contains date information, two dates representing the same day but with different time will have the same key.
   */
  key: string;
  /**
   * The timestamp of the date.
   */
  timestamp: number;
}

/**
 * Properties to pass to the methods that update an event (recurring or not).
 * The `id`, `start` and `end` properties are required in order to identify the event to update and the new dates.
 * All other properties are optional and can be skipped if not modified.
 */
export type SchedulerEventUpdatedProperties = Partial<SchedulerEvent> & {
  id: SchedulerEventId;
};

/**
 * Properties to pass to the methods that create a new event.
 * The `id` property is omitted as it will be generated by the store.
 */
export type SchedulerEventCreationProperties = Omit<SchedulerEvent, 'id'>;

/**
 * Properties to pass to the methods that paste an event.
 */
export type SchedulerEventPasteProperties = Partial<
  Pick<SchedulerEvent, 'start' | 'resource' | 'allDay'>
>;

// TODO: Consider splitting the interface in two, one for the Event Calendar and one for the Timeline.
/**
 * The type of surface the event is being rendered on.
 */
export type EventSurfaceType = 'day-grid' | 'time-grid' | 'timeline';

export type SchedulerEventModelStructure<TEvent extends object> = {
  [key in keyof SchedulerEvent]?: {
    getter: (event: TEvent) => SchedulerEvent[key];
    /**
     * Setter for the event property.
     * If not provided, the property won't be editable.
     */
    setter?: (
      event: TEvent | Partial<TEvent>,
      value: SchedulerEvent[key],
    ) => TEvent | Partial<TEvent>;
  };
};

export interface SchedulerEventCreationConfig {
  /**
   * The interaction required to create an event.
   * @default 'double-click'
   */
  interaction: 'click' | 'double-click';
  /**
   * The default duration (in minutes) of the created event.
   * @default 30
   */
  duration: number;
}
